import sys
import os
import re
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *

class Highlighter(QSyntaxHighlighter):
    def __init__(self, parent=None):
        QSyntaxHighlighter.__init__(self, parent)

        self._mappings = {}

    def add_mapping(self, pattern, format):
        self._mappings[pattern] = format

    def highlightBlock(self, text):
        for pattern, format in self._mappings.items():
            for match in re.finditer(pattern, text):
                start, end = match.span()
                self.setFormat(start, end - start, format)

class LineNumberArea(QWidget):
    def __init__(self, editor):
        super(LineNumberArea, self).__init__(editor)
        self._code_editor = editor

    def sizeHint(self):
        return QSize(self._code_editor.line_number_area_width(), 0)

    def paintEvent(self, event):
        self._code_editor.lineNumberAreaPaintEvent(event)

class CodeEditor(QPlainTextEdit):
    DARK_BLUE = QColor(118, 150, 185)  #行号颜色  
    keywords = [
        'and', 'assert', 'break', 'class', 'continue', 'def',
        'del', 'elif', 'else', 'except', 'exec', 'finally',
        'for', 'from', 'global', 'if', 'import', 'in',
        'is', 'lambda', 'not', 'or', 'pass', 'print',
        'raise', 'return', 'try', 'while', 'yield',
        'None', 'True', 'False',
    ]
    operators = [
        r'=',        
        r'==', r'!=', r'<', r'<=', r'>', r'>=',        
        r'\+', r'-', r'\*', r'/', r'//', r'\%', r'\*\*',        
        r'\+=', r'-=', r'\*=', r'/=', r'\%=',        
        r'\^', r'\|', r'\&', r'\~', r'>>', r'<<',
    ]
    braces = [
        r'\{', r'\}', r'\(', r'\)', r'\[', r'\]',
    ]
    def __init__(self):
        super().__init__()
        self.setReadOnly(True)
        self.line_number_area = LineNumberArea(self)
        self._highlighter = Highlighter()

        self_format = QTextCharFormat()
        self_format.setFontItalic(True)
        self_format.setForeground(Qt.GlobalColor.black)        
        self._highlighter.add_mapping(r'\bself\b', self_format)
        
        keyword_Format=QTextCharFormat()
        keyword_Format.setForeground(Qt.GlobalColor.blue)        
        for w in self.keywords:            
            self._highlighter.add_mapping(r'\b%s\b'%w, keyword_Format)

        operator_Format=QTextCharFormat()        
        operator_Format.setForeground(Qt.GlobalColor.red)        
        for o in self.operators:                        
            self._highlighter.add_mapping(r'%s'%o, operator_Format)

        brace_Format=QTextCharFormat()        
        brace_Format.setForeground(Qt.GlobalColor.darkGray)        
        for b in self.braces:
            self._highlighter.add_mapping(r'%s'%b, brace_Format)

        classAndfunction_format = QTextCharFormat()
        classAndfunction_format.setFontWeight(QFont.Bold)
        classAndfunction_format.setForeground(Qt.GlobalColor.black)
        self._highlighter.add_mapping(r'(?<=class)\b\s*(\w+)', classAndfunction_format)   
        self._highlighter.add_mapping(r'(?<=def)\b\s*(\w+)', classAndfunction_format)

        string_format = QTextCharFormat()
        string_format.setForeground(Qt.GlobalColor.magenta)              
        #self._highlighter.add_mapping(r'"""[^"\\]*(\\.[^"\\]*)*"""', string_format)#换行或不换行字符串
        #self._highlighter.add_mapping(r"'''[^'\\]*(\\.[^'\\]*)*'''", string_format)
        self._highlighter.add_mapping(r'"[^"].*"', string_format)#不换行字符串
        self._highlighter.add_mapping(r"'[^'].*'", string_format)        

        number_format = QTextCharFormat()
        _color=QColor()
        _color.setNamedColor('brown')
        number_format.setForeground(_color)
        self._highlighter.add_mapping(r'\b[+-]?[0-9]+[lL]?\b', number_format)
        self._highlighter.add_mapping(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', number_format)
        self._highlighter.add_mapping(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', number_format)
        
        comment_format = QTextCharFormat()
        comment_format.setForeground(Qt.GlobalColor.darkGreen)
        comment_format.setFontItalic(True)
        self._highlighter.add_mapping(r'#[^\n]*', comment_format)
        self._highlighter.add_mapping(r'"""[^"\\]*(\\.[^"\\]*)*"""', comment_format)
        self._highlighter.add_mapping(r"'''[^'\\]*(\\.[^'\\]*)*'''", comment_format)
        

        self.font = QFont()
        self.font.setFamily("Courier")
        self.font.setStyleHint(QFont.Monospace)
        self.font.setPointSize(10)
        self.setFont(self.font)

        self.tab_size = 4        
        self.setTabStopDistance(self.tab_size * self.fontMetrics().horizontalAdvance(' '))

        self.blockCountChanged.connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.cursorPositionChanged.connect(self.highlight_current_line)

        self.update_line_number_area_width(0)
        self.highlight_current_line()

        self._highlighter.setDocument(self.document())     
    def line_number_area_width(self):
        digits = 1
        max_num = max(1, self.blockCount())
        while max_num >= 10:
            max_num *= 0.1
            digits += 1
        
        space = 30 + self.fontMetrics().horizontalAdvance('9') * digits
        return space

    def resizeEvent(self, e):
        super(CodeEditor, self).resizeEvent(e)
        cr = self.contentsRect()
        width = self.line_number_area_width()
        rect = QRect(cr.left(), cr.top(), width, cr.height())
        self.line_number_area.setGeometry(rect)

    def lineNumberAreaPaintEvent(self, event):
        painter = QPainter(self.line_number_area)
        block = self.firstVisibleBlock()
        block_number = block.blockNumber()+1#默认从0开始
        offset = self.contentOffset()
        top = self.blockBoundingGeometry(block).translated(offset).top()
        bottom = top + self.blockBoundingRect(block).height()

        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                number = str(block_number)
                painter.setPen(self.DARK_BLUE)
                width = self.line_number_area.width() - 10
                height = self.fontMetrics().height()
                painter.drawText(0, top, width, height, Qt.AlignRight, number)

            block = block.next()
            top = bottom
            bottom = top + self.blockBoundingRect(block).height()
            block_number += 1

    def update_line_number_area_width(self, newBlockCount):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            width = self.line_number_area.width()
            self.line_number_area.update(0, rect.y(), width, rect.height())

        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width(0)

    def highlight_current_line(self):
        extra_selections = list()
        if not self.isReadOnly():
            selection = QTextEdit.ExtraSelection()
            line_color = self.DARK_BLUE.lighter(160)
            selection.format.setBackground(line_color)
            selection.format.setProperty(QTextFormat.FullWidthSelection, True)

            selection.cursor = self.textCursor()
            selection.cursor.clearSelection()
            extra_selections.append(selection)
        self.setExtraSelections(extra_selections)   

    def setText(self,text):
        self.setPlainText(text)
           